home *** CD-ROM | disk | FTP | other *** search
/ Programmer Plus 2007 / Programmer-Plus-2007.iso / Programming / XML Utilities / Professional Programmer XSL IDE / Xselerator25.msi / Data.Cab / F25588_SimplePieGraph.xsl < prev    next >
Encoding:
Extensible Markup Language  |  2002-03-27  |  15.6 KB  |  331 lines

  1. <?xml version="1.0"?>
  2. <!-- ========================================================= -->
  3. <!-- Stylesheet for showing how to draw a simple pie chart     -->
  4. <!-- from XML data.                                            -->
  5. <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
  6. <!-- Written by Martin "Marrow" Rowlinson                      -->
  7. <!--            Marrowsoft Ltd. England                        -->
  8. <!-- With special thanks to Dimitre Novatchev for the          -->
  9. <!-- trigonometric functions library (trig_lib.xsl).           -->
  10. <!-- ========================================================= -->
  11. <xsl:stylesheet version="1.0"
  12.   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  13.   xmlns:svg="http://www.w3.org/2000/svg">
  14. <xsl:output method="xml" indent="yes" media-type="text/xml+svg"/>
  15.  
  16. <!-- include trig functions -->
  17. <xsl:include href="trig_lib.xsl"/>
  18.  
  19. <!-- define the size of the pie chart -->
  20. <xsl:param name="pie_radiusX" select="number(200)"/>
  21. <xsl:param name="pie_radiusY" select="$pie_radiusX"/>
  22. <xsl:param name="pie_explode_radiusX" select="number(20)"/>
  23. <xsl:param name="pie_explode_radiusY" select="$pie_explode_radiusX"/>
  24. <!-- define the center position of the pie chart -->
  25. <xsl:param name="pie_centerX" select="$pie_radiusX + 150"/>
  26. <xsl:param name="pie_centerY" select="$pie_radiusY + 50"/>
  27. <!-- define a segment to explode                 -->
  28. <!-- set to n to explode that segment (by order) -->
  29. <!-- 0 to explode none                           -->
  30. <!-- -1 to explode all                           -->
  31. <!-- -2 to explode 'others' (grouped segment)    -->
  32. <xsl:param name="pie_segment_explode" select="number(0)"/>
  33. <!-- define the start angle of the first pie segment -->
  34. <xsl:param name="pie_start_angle" select="number(0)"/>
  35. <!-- define the threshold at which below this % a segment -->
  36. <!-- is not shown individually but grouped                -->
  37. <xsl:param name="pie_lowest_show_percent" select="number(5)"/>
  38. <!-- pie chart title -->
  39. <xsl:param name="pie_title" select="'% Sales by Product ID'"/>
  40.  
  41. <!-- key used for grouping the product sales by @product_id -->
  42. <xsl:key name="kProdSales" match="product_sales" use="@product_id"/>
  43.  
  44. <!-- ========================================================= -->
  45. <!-- Main stylesheet template                                  -->
  46. <xsl:template match="/">
  47.   <!-- get list of distinct products -->
  48.   <xsl:variable name="ProdSales" select="sales_summary/weekly_sales/product_sales[generate-id() = generate-id(key('kProdSales',@product_id))]"/>
  49.   <!-- get total value of all product sales -->
  50.   <xsl:variable name="TotalSalesValue" select="sum(sales_summary/weekly_sales/product_sales/@value)"/>
  51.   <!-- do the svg root stuff -->
  52.   <svg:svg id="body" viewBox="0 0 -100 {$pie_centerY + $pie_radiusY + 75}">
  53.     <!-- call recursive template to work though the products -->
  54.     <xsl:call-template name="do_segments">
  55.       <xsl:with-param name="distinct_prod_sales" select="$ProdSales"/>
  56.       <xsl:with-param name="total_sales" select="$TotalSalesValue"/>
  57.     </xsl:call-template>
  58.     <!-- draw the pie chart title -->
  59.     <svg:text text-anchor="middle" font-size-adjust="+1" x="{$pie_centerX - $pie_radiusX}" y="{$pie_centerY + $pie_radiusY + 50}" dx="{2 * $pie_radiusX}">
  60.       <xsl:value-of select="$pie_title"/>
  61.     </svg:text>
  62.   </svg:svg>
  63. </xsl:template>
  64.  
  65. <!-- ========================================================= -->
  66. <!-- Recursive template used to draw each segment sequentially -->
  67. <!-- Notes:                                                    -->
  68. <!-- 1) the template is recursed so that some segments may be  -->
  69. <!--    skipped where their size falls below a display min     -->
  70. <!--    threshold.                                             -->
  71. <!-- 2) a divide and conquer may be better - but it is assumed -->
  72. <!--    that the segments to be drawn would not be large - and -->
  73. <!--    to keep this demo straight forward as possible.        -->
  74. <xsl:template name="do_segments">
  75.   <xsl:param name="distinct_prod_sales"/>
  76.   <xsl:param name="total_sales"/>
  77.   <!-- params used in recursive calls -->
  78.   <xsl:param name="total_perc_so_far" select="number(0)"/>
  79.   <xsl:param name="_i" select="number(1)"/> <!-- used to track actual indice within node-set -->
  80.   <xsl:param name="_i2" select="number(1)"/> <!-- only incremented when a segement is actually drawn --> 
  81.   <xsl:param name="_left_overs_perc" select="number(0)"/>
  82.   <!-- are we handling the left overs or an actual product -->
  83.   <xsl:choose>
  84.     <xsl:when test="$distinct_prod_sales[$_i]">
  85.       <!-- calc total of sales for this product id -->
  86.       <xsl:variable name="this_prod_sales" select="sum(key('kProdSales',$distinct_prod_sales[$_i]/@product_id)/@value)"/>
  87.       <!-- calc the percentage of this product sales over total -->
  88.       <xsl:variable name="perc_of_total" select="$this_prod_sales div $total_sales"/>
  89.       <!-- actual product - decide whether it is below draw segment threshold -->
  90.       <xsl:choose>
  91.         <xsl:when test="$perc_of_total < ($pie_lowest_show_percent div 100)">
  92.           <!-- skip it and show it bunched with rest of below thresholds -->
  93.           <xsl:call-template name="do_segments">
  94.             <xsl:with-param name="distinct_prod_sales" select="$distinct_prod_sales"/>
  95.             <xsl:with-param name="total_sales" select="$total_sales"/>
  96.             <xsl:with-param name="total_perc_so_far" select="$total_perc_so_far"/>
  97.             <xsl:with-param name="_i" select="$_i + 1"/>
  98.             <xsl:with-param name="_i2" select="$_i2"/>
  99.             <xsl:with-param name="_left_overs_perc" select="$_left_overs_perc + $perc_of_total"/>
  100.           </xsl:call-template>
  101.         </xsl:when>
  102.         <xsl:otherwise>
  103.           <!-- draw it -->
  104.           <xsl:call-template name="draw_pie_segment">
  105.             <xsl:with-param name="angle" select="360 * $perc_of_total"/>
  106.             <xsl:with-param name="prev_angle" select="$pie_start_angle + (360 * $total_perc_so_far)"/>
  107.             <xsl:with-param name="label" select="concat($distinct_prod_sales[$_i]/@product_id,' (',format-number($perc_of_total * 100,'0.0'),'%)')"/>
  108.             <xsl:with-param name="explode" select="($pie_segment_explode = $_i2) or ($pie_segment_explode = -1)"/>
  109.             <xsl:with-param name="fill_color">
  110.               <xsl:call-template name="calc_color">
  111.                 <xsl:with-param name="rel_position" select="$_i2"/>
  112.               </xsl:call-template>
  113.             </xsl:with-param>
  114.             <xsl:with-param name="radiusX" select="$pie_radiusX"/>
  115.             <xsl:with-param name="radiusY" select="$pie_radiusY"/>
  116.             <xsl:with-param name="centerX" select="$pie_centerX"/>
  117.             <xsl:with-param name="centerY" select="$pie_centerY"/>
  118.             <xsl:with-param name="explode_radiusX" select="$pie_explode_radiusX"/>
  119.             <xsl:with-param name="explode_radiusY" select="$pie_explode_radiusY"/>
  120.           </xsl:call-template>
  121.           <!-- and do the next segment (or any left overs) -->
  122.           <xsl:call-template name="do_segments">
  123.             <xsl:with-param name="distinct_prod_sales" select="$distinct_prod_sales"/>
  124.             <xsl:with-param name="total_sales" select="$total_sales"/>
  125.             <xsl:with-param name="total_perc_so_far" select="$total_perc_so_far + $perc_of_total"/>
  126.             <xsl:with-param name="_i" select="$_i + 1"/>
  127.             <xsl:with-param name="_i2" select="$_i2 + 1"/>
  128.             <xsl:with-param name="_left_overs_perc" select="$_left_overs_perc"/>
  129.           </xsl:call-template>
  130.         </xsl:otherwise>
  131.       </xsl:choose>
  132.     </xsl:when>
  133.     <xsl:when test="$_left_overs_perc > 0">
  134.       <!-- left overs - with some to actually do -->
  135.       <xsl:call-template name="draw_pie_segment">
  136.         <xsl:with-param name="angle" select="360 * $_left_overs_perc"/>
  137.         <xsl:with-param name="prev_angle" select="$pie_start_angle + (360 * $total_perc_so_far)"/>
  138.         <xsl:with-param name="label" select="concat('Others (',format-number($_left_overs_perc * 100,'0.0'),'%)')"/>
  139.         <xsl:with-param name="explode" select="($pie_segment_explode < 0)"/>
  140.         <xsl:with-param name="fill_color">
  141.           <xsl:call-template name="calc_color">
  142.             <xsl:with-param name="rel_position" select="$_i2"/>
  143.           </xsl:call-template>
  144.         </xsl:with-param>
  145.         <xsl:with-param name="radiusX" select="$pie_radiusX"/>
  146.         <xsl:with-param name="radiusY" select="$pie_radiusY"/>
  147.         <xsl:with-param name="centerX" select="$pie_centerX"/>
  148.         <xsl:with-param name="centerY" select="$pie_centerY"/>
  149.         <xsl:with-param name="explode_radiusX" select="$pie_explode_radiusX"/>
  150.         <xsl:with-param name="explode_radiusY" select="$pie_explode_radiusY"/>
  151.       </xsl:call-template>
  152.     </xsl:when>
  153.   </xsl:choose>
  154. </xsl:template>
  155.  
  156. <!-- ========================================================= -->
  157. <!-- Template for drawing a single pie chart segment           -->
  158. <!-- Nnote: All info that this template requires for drawing   -->
  159. <!--        the segment is passed in as a parameter.  Thus,    -->
  160. <!--        this template is re-usable to some extent.         -->
  161. <xsl:template name="draw_pie_segment">
  162.   <xsl:param name="angle"/>
  163.   <xsl:param name="prev_angle"/>
  164.   <xsl:param name="label"/>
  165.   <xsl:param name="explode" select="false()"/>
  166.   <xsl:param name="fill_color" select="'red'"/>
  167.   <xsl:param name="line_color" select="'black'"/>
  168.   <xsl:param name="radiusX" select="number(200)"/>
  169.   <xsl:param name="radiusY" select="number(200)"/>
  170.   <xsl:param name="centerX" select="$radiusX + 50"/>
  171.   <xsl:param name="centerY" select="$radiusY + 50"/>
  172.   <xsl:param name="explode_radiusX" select="number(20)"/>
  173.   <xsl:param name="explode_radiusY" select="number(20)"/>
  174.   <!-- calc the full angle -->
  175.   <xsl:variable name="full_angle" select="$prev_angle + $angle"/>
  176.   <!-- calc the half angle - for use with labels and exploded segments -->
  177.   <xsl:variable name="half_angle" select="$prev_angle + ($angle div 2)"/>
  178.   <!-- calc things that change according to explode segment status -->
  179.   <xsl:variable name="X0">
  180.     <xsl:choose>
  181.       <xsl:when test="$explode">
  182.         <xsl:variable name="sinX">
  183.           <xsl:call-template name="sin">
  184.             <xsl:with-param name="pX" select="$half_angle"/>
  185.             <xsl:with-param name="pUnit" select="'deg'"/>
  186.           </xsl:call-template>
  187.         </xsl:variable>
  188.         <xsl:value-of select="$centerX + ($sinX * $explode_radiusX)"/>
  189.       </xsl:when>
  190.       <xsl:otherwise>
  191.         <xsl:value-of select="$centerX"/>
  192.       </xsl:otherwise>
  193.     </xsl:choose>
  194.   </xsl:variable>
  195.   <xsl:variable name="Y0">
  196.     <xsl:choose>
  197.       <xsl:when test="$explode">
  198.         <xsl:variable name="cosX">
  199.           <xsl:call-template name="cos">
  200.             <xsl:with-param name="pX" select="$half_angle"/>
  201.             <xsl:with-param name="pUnit" select="'deg'"/>
  202.           </xsl:call-template>
  203.         </xsl:variable>
  204.         <xsl:value-of select="$centerY + (0 - ($cosX * $explode_radiusY))"/>
  205.       </xsl:when>
  206.       <xsl:otherwise>
  207.         <xsl:value-of select="$centerY"/>
  208.       </xsl:otherwise>
  209.     </xsl:choose>
  210.   </xsl:variable>
  211.   <!-- calc the co-ordinates of the important points of the segment -->
  212.   <xsl:variable name="X1">
  213.     <xsl:variable name="sinX">
  214.       <xsl:call-template name="sin">
  215.         <xsl:with-param name="pX" select="$prev_angle"/>
  216.         <xsl:with-param name="pUnit" select="'deg'"/>
  217.       </xsl:call-template>
  218.     </xsl:variable>
  219.     <xsl:value-of select="($sinX * $radiusX)"/>
  220.   </xsl:variable>
  221.   <xsl:variable name="Y1">
  222.     <xsl:variable name="cosX">
  223.       <xsl:call-template name="cos">
  224.         <xsl:with-param name="pX" select="$prev_angle"/>
  225.         <xsl:with-param name="pUnit" select="'deg'"/>
  226.       </xsl:call-template>
  227.     </xsl:variable>
  228.     <xsl:value-of select="0 - ($cosX * $radiusY)"/>
  229.   </xsl:variable>
  230.   <xsl:variable name="X2">
  231.     <xsl:variable name="sinX">
  232.       <xsl:call-template name="sin">
  233.         <xsl:with-param name="pX" select="$full_angle"/>
  234.         <xsl:with-param name="pUnit" select="'deg'"/>
  235.       </xsl:call-template>
  236.     </xsl:variable>
  237.     <xsl:value-of select="($sinX * $radiusX) - $X1"/>
  238.   </xsl:variable>
  239.   <xsl:variable name="Y2">
  240.     <xsl:variable name="cosX">
  241.       <xsl:call-template name="cos">
  242.         <xsl:with-param name="pX" select="$full_angle"/>
  243.         <xsl:with-param name="pUnit" select="'deg'"/>
  244.       </xsl:call-template>
  245.     </xsl:variable>
  246.     <xsl:value-of select="(0 - ($cosX * $radiusY)) - $Y1"/>
  247.   </xsl:variable>
  248.   <!-- for the label - work out the quadrant in which it will appear -->
  249.   <xsl:variable name="label_quandrant" select="floor(($half_angle mod 360) div 90)"/>
  250.   <xsl:variable name="textXalign">
  251.     <xsl:choose>
  252.       <xsl:when test="$label_quandrant < 2">
  253.         <xsl:text>start</xsl:text>
  254.       </xsl:when>
  255.       <xsl:otherwise>
  256.         <xsl:text>end</xsl:text>
  257.       </xsl:otherwise>
  258.     </xsl:choose>
  259.   </xsl:variable>
  260.   <xsl:variable name="textDY">
  261.     <xsl:choose>
  262.       <xsl:when test="($label_quandrant = 0) or ($label_quandrant = 3)">
  263.         <xsl:text>-10</xsl:text>
  264.       </xsl:when>
  265.       <xsl:otherwise>
  266.         <xsl:text>15</xsl:text>
  267.       </xsl:otherwise>
  268.     </xsl:choose>
  269.   </xsl:variable>
  270.   <!-- calc X and Y posn for the label -->
  271.   <xsl:variable name="XTxt">
  272.     <xsl:variable name="sinX">
  273.       <xsl:call-template name="sin">
  274.         <xsl:with-param name="pX" select="$half_angle"/>
  275.         <xsl:with-param name="pUnit" select="'deg'"/>
  276.       </xsl:call-template>
  277.     </xsl:variable>
  278.     <xsl:value-of select="$X0 + ($sinX * ($radiusX + 10))"/>
  279.   </xsl:variable>
  280.   <xsl:variable name="YTxt">
  281.     <xsl:variable name="cosX">
  282.       <xsl:call-template name="cos">
  283.         <xsl:with-param name="pX" select="$half_angle"/>
  284.         <xsl:with-param name="pUnit" select="'deg'"/>
  285.       </xsl:call-template>
  286.     </xsl:variable>
  287.     <xsl:value-of select="$Y0 + (0 - ($cosX * $radiusY))"/>
  288.   </xsl:variable>
  289.   <!-- do the actual svg -->
  290.   <svg:g id="{$label}">
  291.     <!-- draw the segment itself -->
  292.     <svg:path fill="{$fill_color}" stroke="{$line_color}" d="M{$X0},{$Y0} l{$X1},{$Y1} a{$radiusX},{$radiusY} 0 0 1 {$X2},{$Y2} L{$X0},{$Y0}"/>
  293.     <!-- draw the segment label -->
  294.     <svg:text text-anchor="{$textXalign}" x="{$XTxt}" y="{$YTxt}" dy="{$textDY}">
  295.       <xsl:value-of select="$label"/>
  296.     </svg:text>
  297.   </svg:g>
  298. </xsl:template>
  299.  
  300. <!-- ========================================================= -->
  301. <!-- Called template for calculating colors                    -->
  302. <!-- this is a crude way of giving each segment a different    -->
  303. <!-- color - calculated according to a relative position       -->
  304. <xsl:template name="calc_color">
  305.   <xsl:param name="rel_position" select="number(0)"/>
  306.   <xsl:variable name="color_lightnesses" select="'F0D0B09070503010E0C0A08060402000'"/>
  307.   <xsl:text>#</xsl:text>
  308.   <!-- red part -->
  309.   <xsl:choose>
  310.     <xsl:when test="($rel_position mod 3) = 0">F0</xsl:when>
  311.     <xsl:otherwise>
  312.       <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
  313.     </xsl:otherwise>
  314.   </xsl:choose>
  315.   <!-- blue part -->
  316.   <xsl:choose>
  317.     <xsl:when test="($rel_position mod 3) = 1">F0</xsl:when>
  318.     <xsl:otherwise>
  319.       <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
  320.     </xsl:otherwise>
  321.   </xsl:choose>
  322.   <!-- green part -->
  323.   <xsl:choose>
  324.     <xsl:when test="($rel_position mod 3) = 2">F0</xsl:when>
  325.     <xsl:otherwise>
  326.       <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
  327.     </xsl:otherwise>
  328.   </xsl:choose>
  329. </xsl:template>
  330.  
  331. </xsl:stylesheet>